home *** CD-ROM | disk | FTP | other *** search
/ Power Programmierung 2 / Power-Programmierung CD 2 (Tewi)(1994).iso / gnu / gnulib / ohlutil / tac.c < prev    next >
Encoding:
C/C++ Source or Header  |  1990-08-24  |  17.6 KB  |  667 lines

  1. /* tac - concatenate and print files in reverse
  2.    Copyright (C) 1988, 1989, 1990 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 1, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Written by Jay Lepreau (lepreau@cs.utah.edu).
  19.    GNU enhancements by David MacKenzie (djm@ai.mit.edu). */
  20.  
  21. /*
  22.  * MS-DOS port (c) 1990 by Thorsten Ohl, td12@ddagsi3.bitnet
  23.  *
  24.  * To this port, the same copying conditions apply as to the
  25.  * original release.
  26.  *
  27.  * IMPORTANT:
  28.  * This file is not identical to the original GNU release!
  29.  * You should have received this code as patch to the official
  30.  * GNU release.
  31.  *
  32.  * MORE IMPORTANT:
  33.  * This port comes with ABSOLUTELY NO WARRANTY.
  34.  *
  35.  * $Header: e:/gnu/fileutil/RCS/tac.c'v 1.3.0.3 90/06/29 02:46:13 tho Exp $
  36.  */
  37.  
  38. /* Usage: tac [-br] [-s separator] [+before] [+regex] [+separator separator]
  39.           [file...]
  40.  
  41.    Copy each FILE, or the standard input if none are given or when a
  42.    FILE name of "-" is encountered, to the standard output with the
  43.    order of the records reversed.  The records are separated by
  44.    instances of a string, or a newline if none is given.  By default, the
  45.    separator string is attached to the end of the record that it
  46.    follows in the file.
  47.  
  48.    Options:
  49.    -b, +before            The separator is attached to the beginning
  50.                 of the record that it precedes in the file.
  51.    -r, +regex            The separator is a regular expression.
  52.    -s, +separator separator    Use SEPARATOR as the record separator.
  53.  
  54.    To reverse a file byte by byte, use (in bash, ksh, or sh):
  55. tac -r -s '.\|
  56. ' file */
  57.  
  58. #include <stdio.h>
  59. #include <signal.h>
  60. #include <sys/types.h>
  61. #include "system.h"
  62. #include "getopt.h"
  63. #include "regex.h"
  64.  
  65. #ifdef STDC_HEADERS
  66. #include <stdlib.h>
  67. #include <errno.h>
  68. #else
  69. char *malloc ();
  70. char *realloc ();
  71.  
  72. extern int errno;
  73. #endif
  74.  
  75. /* The number of bytes per atomic read. */
  76. #define INITIAL_READSIZE 8192
  77.  
  78. /* The number of bytes per atomic write. */
  79. #define WRITESIZE 8192
  80.  
  81. char *mktemp ();
  82.  
  83. #ifndef _POSIX_SOURCE
  84. off_t lseek ();
  85. #endif
  86.  
  87. #ifdef MSDOS
  88. #include <string.h>
  89. #include <malloc.h>
  90. #include <io.h>
  91.  
  92. extern void error (int status, int errnum, char *message, ...);
  93.  
  94. extern void main (int argc, char **argv);
  95. extern int tac_stdin (void );
  96. extern void save_stdin (void );
  97. extern int tac_file (char *file);
  98. extern int tac (int fd, char *file);
  99. extern void output (char *start, char *past_end);
  100. extern void cleanup (void );
  101. extern void xwrite (int desc, char *buffer, int size);
  102. extern char *xmalloc (unsigned int n);
  103. extern char *xrealloc (char *p, unsigned int n);
  104. #else /* not MSDOS */
  105. void cleanup ();
  106. int tac ();
  107. int tac_file ();
  108. int tac_stdin ();
  109. char *xmalloc ();
  110. char *xrealloc ();
  111. void output ();
  112. void error ();
  113. void save_stdin ();
  114. void xwrite ();
  115. #endif /* not MSDOS */
  116.  
  117. /* The name this program was run with. */
  118. char *program_name;
  119.  
  120. /* The string that separates the records of the file. */
  121. char *separator;
  122.  
  123. /* If nonzero, print `separator' along with the record preceding it
  124.    in the file; otherwise with the record following it. */
  125. int separator_ends_record;
  126.  
  127. /* 0 if `separator' is to be matched as a regular expression;
  128.    otherwise, the length of `separator', used as a sentinel to
  129.    stop the search. */
  130. int sentinel_length;
  131.  
  132. /* The length of a match with `separator'.  If `sentinel_length' is 0,
  133.    `match_length' is computed every time a match succeeds;
  134.    otherwise, it is simply the length of `separator'. */
  135. int match_length;
  136.  
  137. /* The input buffer. */
  138. char *buffer;
  139.  
  140. /* The number of bytes to read at once into `buffer'. */
  141. unsigned read_size;
  142.  
  143. /* The size of `buffer'.  This is read_size * 2 + sentinel_length + 2.
  144.    The extra 2 bytes allow `past_end' to have a value beyond the
  145.    end of `buffer' and `match_start' to run off the front of `buffer'. */
  146. unsigned buffer_size;
  147.  
  148. /* The compiled regular expression representing `separator'. */
  149. static struct re_pattern_buffer compiled_separator;
  150.  
  151. struct option longopts[] =
  152. {
  153.   {"before", 0, &separator_ends_record, 0},
  154.   {"regex", 0, &sentinel_length, 0},
  155.   {"separator", 1, NULL, 's'},
  156.   {NULL, 0, NULL, 0}
  157. };
  158.  
  159. void
  160. main (argc, argv)
  161.      int argc;
  162.      char **argv;
  163. {
  164.   char *error_message;        /* The return value from re_compile. */
  165.   int optc, longind, errors;
  166.  
  167.   program_name = argv[0];
  168. #ifdef MSDOS
  169.   strlwr (program_name);
  170.   setmode (0, O_BINARY);
  171.   setmode (1, O_BINARY);
  172. #endif
  173.   errors = 0;
  174.   separator = "\n";
  175.   sentinel_length = 1;
  176.   separator_ends_record = 1;
  177.  
  178.   while ((optc = getopt_long (argc, argv, "brs:", longopts, &longind))
  179.      != EOF)
  180.     {
  181.       if (optc == 0 && longopts[longind].flag == NULL)
  182.     optc = longopts[longind].val;
  183.       switch (optc)
  184.     {
  185.     case 0:
  186.       break;
  187.     case 'b':
  188.       separator_ends_record = 0;
  189.       break;
  190.     case 'r':
  191.       sentinel_length = 0;
  192.       break;
  193.     case 's':
  194.       separator = optarg;
  195.       if (*separator == 0)
  196.         error (1, 0, "separator cannot be empty");
  197.       break;
  198.     default:
  199.       fprintf (stderr, "\
  200. Usage: %s [-br] [-s separator] [+before] [+regex] [+separator separator]\n\
  201.        [file...]\n",
  202.            program_name);
  203.       exit (1);
  204.     }
  205.     }
  206.  
  207.   if (sentinel_length == 0)
  208.     {
  209.       compiled_separator.allocated = 100;
  210. #ifdef MSDOS
  211.       compiled_separator.buffer
  212.     = xmalloc ((size_t) compiled_separator.allocated);
  213. #else /* not MSDOS */
  214.       compiled_separator.buffer = xmalloc (compiled_separator.allocated);
  215. #endif /* not MSDOS */
  216.       compiled_separator.translate = 0;
  217.       error_message = re_compile_pattern (separator, strlen (separator),
  218.                       &compiled_separator);
  219.       if (error_message)
  220.     error (1, 0, "%s", error_message);
  221.       compiled_separator.fastmap = xmalloc (256);
  222.     }
  223.   else
  224.     match_length = sentinel_length = strlen (separator);
  225.  
  226.   read_size = INITIAL_READSIZE;
  227.   /* A precaution that will probably never be needed. */
  228.   while (sentinel_length * 2 >= read_size)
  229.     read_size *= 2;
  230.   buffer_size = read_size * 2 + sentinel_length + 2;
  231.   buffer = xmalloc (buffer_size);
  232.   if (sentinel_length)
  233.     {
  234.       strcpy (buffer, separator);
  235.       buffer += sentinel_length;
  236.     }
  237.   else
  238.     ++buffer;
  239.  
  240.   if (optind == argc)
  241.     errors = tac_stdin ();
  242.   else
  243.     for (; optind < argc; ++optind)
  244.       {
  245.     if (strcmp (argv[optind], "-") == 0)
  246.       errors |= tac_stdin ();
  247.     else
  248. #ifdef MSDOS
  249.       errors |= tac_file (strlwr (argv[optind]));
  250. #else /* not MSDOS */
  251.       errors |= tac_file (argv[optind]);
  252. #endif /* not MSDOS */
  253.       }
  254.  
  255.   /* Flush the output buffer. */
  256.   output ((char *) NULL, (char *) NULL);
  257.   exit (errors);
  258. }
  259.  
  260. /* The name of a temporary file containing a copy of pipe input. */
  261. char *tempfile;
  262.  
  263. /* Print the standard input in reverse, saving it to temporary
  264.    file `tempfile' first if it is a pipe.
  265.    Return 0 if ok, 1 if an error occurs. */
  266.  
  267. int
  268. tac_stdin ()
  269. {
  270.   /* Previous values of signal handlers. */
  271.   SIGTYPE (*sigint) (), (*sighup) (), (*sigterm) ();
  272.   int errors;
  273.  
  274.   /* No tempfile is needed for "tac < file". */
  275.   if (lseek (0, (off_t) 0, 0) == 0)
  276.     return tac (0, "standard input");
  277.  
  278.   sigint = signal (SIGINT, SIG_IGN);
  279.   if (sigint != SIG_IGN)
  280.     signal (SIGINT, cleanup);
  281. #ifdef SIGHUP
  282.   sighup = signal (SIGHUP, SIG_IGN);
  283.   if (sighup != SIG_IGN)
  284.     signal (SIGHUP, cleanup);
  285. #endif
  286.   sigterm = signal (SIGTERM, SIG_IGN);
  287.   if (sigterm != SIG_IGN)
  288.     signal (SIGTERM, cleanup);
  289.   save_stdin ();
  290.  
  291.   errors = tac_file (tempfile);
  292.  
  293.   unlink (tempfile);
  294.   signal (SIGINT, sigint);
  295. #ifdef SIGHUP
  296.   signal (SIGHUP, sighup);
  297. #endif
  298.   signal (SIGTERM, sigterm);
  299.   return errors;
  300. }
  301.  
  302. #ifdef MSDOS
  303. char template[] = "/tacXXXXXX";
  304. char *workplate;
  305. #else /* not MSDOS */
  306. char template[] = "/tmp/tacXXXXXX";
  307. char workplate[sizeof template];
  308. #endif /* not MSDOS */
  309.  
  310. /* Make a copy of the standard input in `tempfile'. */
  311.  
  312. void
  313. save_stdin ()
  314. {
  315.   int fd;
  316.   int bytes_read;
  317.  
  318. #ifdef MSDOS
  319.   char *p;
  320.  
  321.   if ((p = getenv ("TMP")) || (p = getenv ("TEMP")))
  322.     {
  323.       int len = strlen (p);
  324.       workplate = (char *) alloca (sizeof (template) + len + 1);
  325.       strcpy (workplate, p);
  326.       p = workplate + len - 1;
  327.       if (*p == '/' || *p == '\\')    /*  strip trailing slash */
  328.     *p = '\0';
  329.     }
  330.   else
  331.     {
  332.       workplate = (char *) alloca (sizeof (template) + 2);
  333.       strcpy (workplate, ".");
  334.     }
  335.   strcat (workplate, template);
  336. #else /* not MSDOS */
  337.   strcpy (workplate, template);
  338. #endif /* not MSDOS */
  339.   tempfile = mktemp (workplate);
  340.  
  341. fprintf (stderr, "tempfile: \"%s\"\n", tempfile);
  342.  
  343.   fd = creat (tempfile, 0600);
  344.   if (fd == -1)
  345.     {
  346.       error (0, errno, "%s", tempfile);
  347.       cleanup ();
  348.     }
  349. #ifdef MSDOS
  350.   setmode (fd, O_BINARY);
  351. #endif /* MSDOS */
  352.   while ((bytes_read = read (0, buffer, read_size)) > 0)
  353.     if (write (fd, buffer, bytes_read) != bytes_read)
  354.       {
  355.     error (0, errno, "%s", tempfile);
  356.     cleanup ();
  357.       }
  358.   close (fd);
  359.   if (bytes_read == -1)
  360.     {
  361.       error (0, errno, "read error");
  362.       cleanup ();
  363.     }
  364. }
  365.  
  366. /* Print FILE in reverse.
  367.    Return 0 if ok, 1 if an error occurs. */
  368.  
  369. int
  370. tac_file (file)
  371.      char *file;
  372. {
  373.   int fd, errors;
  374.  
  375. #ifdef MSDOS
  376.   fd = open (file, O_RDONLY|O_BINARY);
  377. #else /* not MSDOS */
  378.   fd = open (file, 0);
  379. #endif /* not MSDOS */
  380.   if (fd == -1)
  381.     {
  382.       error (0, errno, "%s", file);
  383.       return 1;
  384.     }
  385.   errors = tac (fd, file);
  386.   close (fd);
  387.   return errors;
  388. }
  389.  
  390. /* Print in reverse the file open on descriptor FD for reading FILE.
  391.    Return 0 if ok, 1 if an error occurs. */
  392.  
  393. int
  394. tac (fd, file)
  395.      int fd;
  396.      char *file;
  397. {
  398.   /* Pointer to the location in `buffer' where the search for
  399.      the next separator will begin. */
  400.   char *match_start;
  401.   /* Pointer to one past the rightmost character in `buffer' that
  402.      has not been printed yet. */
  403.   char *past_end;
  404.   unsigned saved_record_size;    /* Length of the record growing in `buffer'. */
  405.   off_t file_pos;        /* Offset in the file of the next read. */
  406.   /* Nonzero if `output' has not been called yet for any file.
  407.      Only used when the separator is attached to the preceding record. */
  408.   int first_time = 1;
  409.   char first_char = *separator;    /* Speed optimization, non-regexp. */
  410.   char *separator1 = separator + 1; /* Speed optimization, non-regexp. */
  411.   int match_length1 = match_length - 1; /* Speed optimization, non-regexp. */
  412.   struct re_registers regs;
  413.   int errors = 0;
  414.  
  415.   /* Find the size of the input file. */
  416.   file_pos = lseek (fd, (off_t) 0, 2);
  417.   if (file_pos < 1)
  418.     return 0;            /* It's an empty file. */
  419.  
  420.   /* Arrange for the first read to lop off enough to leave the rest of the
  421.      file a multiple of `read_size'.  Since `read_size' can change, this may
  422.      not always hold during the program run, but since it usually will, leave
  423.      it here for i/o efficiency (page/sector boundaries and all that).
  424.      Note: the efficiency gain has not been verified. */
  425. #ifdef MSDOS
  426.   saved_record_size = (unsigned int) (file_pos % read_size);
  427. #else /* not MSDOS */
  428.   saved_record_size = file_pos % read_size;
  429. #endif /* not MSDOS */
  430.   if (saved_record_size == 0)
  431.     saved_record_size = read_size;
  432.   file_pos -= saved_record_size;
  433.   /* `file_pos' now points to the start of the last (probably partial) block
  434.      in the input file. */
  435.  
  436.   lseek (fd, file_pos, 0);
  437.   if (read (fd, buffer, saved_record_size) != saved_record_size)
  438.     {
  439.       error (0, 1, "%s", file);
  440.       return 1;
  441.     }
  442.  
  443. #ifdef MSDOS
  444.   /* Some loosing editors add one or more ^Z to the end of a text file and
  445.      loosing MS-DOS devices interpret this as End Of File.  We turn them
  446.      into spaces and print a warning message.  */
  447.     {
  448.       char *ctrl_z = buffer + saved_record_size - 1;
  449.       if (*ctrl_z == '\032')
  450.     {
  451.       *ctrl_z = ' ';
  452.       error (0, 0, "%s: trailing ^Z translated to space.", file);
  453.       while (*--ctrl_z == '\032')
  454.         *ctrl_z = ' ';
  455.     }
  456.     }
  457. #endif /* MSDOS */
  458.  
  459.   match_start = past_end = buffer + saved_record_size;
  460.   /* For non-regexp search, move past impossible positions for a match. */
  461.   if (sentinel_length)
  462.     match_start -= match_length1;
  463.  
  464.   for (;;)
  465.     {
  466.       /* Search backward from `match_start' - 1 to `buffer' for a match
  467.      with `separator'; for speed, use strncmp if `separator' contains no
  468.      metacharacters.
  469.      If the match succeeds, set `match_start' to point to the start of
  470.      the match and `match_length' to the length of the match.
  471.      Otherwise, make `match_start' < `buffer'. */
  472.       if (sentinel_length == 0)
  473.     {
  474.       int i = match_start - buffer;
  475.       int ret;
  476.  
  477.       ret = re_search (&compiled_separator, buffer, i, i - 1, -i, ®s);
  478.       if (ret == -1)
  479.         match_start = buffer - 1;
  480.       else if (ret == -2)
  481.         {
  482.           cleanup ();
  483.           error (1, 0, "error in regular expression search");
  484.         }
  485.       else
  486.         {
  487.           match_start = buffer + regs.start[0];
  488.           match_length = regs.end[0] - regs.start[0];
  489.         }
  490.     }
  491.       else
  492.     {
  493.       /* `match_length' is constant for non-regexp boundaries. */
  494.       while (*--match_start != first_char
  495.          || (match_length1 && strncmp (match_start + 1, separator1,
  496.                            match_length1)))
  497.         /* Do nothing. */ ;
  498.     }
  499.  
  500.       /* Check whether we backed off the front of `buffer' without finding
  501.          a match for `separator'. */
  502.       if (match_start < buffer)
  503.     {
  504.       if (file_pos == 0)
  505.         {
  506.           /* Hit the beginning of the file; print the remaining record. */
  507.           output (buffer, past_end);
  508.           return errors;
  509.         }
  510.  
  511.       saved_record_size = past_end - buffer;
  512.       if (saved_record_size > read_size)
  513.         {
  514.           /* `buffer_size' is about twice `read_size', so since
  515.          we want to read in another `read_size' bytes before
  516.          the data already in `buffer', we need to increase
  517.          `buffer_size'. */
  518.           char *newbuffer;
  519.           int offset = sentinel_length ? sentinel_length : 1;
  520.  
  521.           read_size *= 2;
  522.           buffer_size = read_size * 2 + sentinel_length + 2;
  523.           newbuffer = xrealloc (buffer - offset, buffer_size) + offset;
  524.           /* Adjust the pointers for the new buffer location.  */
  525.           match_start += newbuffer - buffer;
  526.           past_end += newbuffer - buffer;
  527.           buffer = newbuffer;
  528.         }
  529.  
  530.       /* Back up to the start of the next bufferfull of the file.  */
  531.       if (file_pos >= read_size)
  532.         file_pos -= read_size;
  533.       else
  534.         {
  535.           read_size = (unsigned) file_pos;
  536.           file_pos = 0;
  537.         }
  538.       lseek (fd, file_pos, 0);
  539.  
  540.       /* Shift the pending record data right to make room for the new. */
  541.       bcopy (buffer, buffer + read_size, saved_record_size);
  542.       past_end = buffer + read_size + saved_record_size;
  543.       /* For non-regexp searches, avoid unneccessary scanning. */
  544.       if (sentinel_length)
  545.         match_start = buffer + read_size;
  546.       else
  547.         match_start = past_end;
  548.  
  549.       if (read (fd, buffer, read_size) != read_size)
  550.         {
  551.           error (0, errno, "%s", file);
  552.           return 1;
  553.         }
  554.     }
  555.       else
  556.     {
  557.       /* Found a match of `separator'. */
  558.       if (separator_ends_record)
  559.         {
  560.           char *match_end = match_start + match_length;
  561.  
  562.           /* If this match of `separator' isn't at the end of the
  563.              file, print the record. */
  564.           if (first_time == 0 || match_end != past_end)
  565.         output (match_end, past_end);
  566.           past_end = match_end;
  567.           first_time = 0;
  568.         }
  569.       else
  570.         {
  571.           output (match_start, past_end);
  572.           past_end = match_start;
  573.         }
  574.       match_start -= match_length - 1;
  575.     }
  576.     }
  577. }
  578.  
  579. /* Print the characters from START to PAST_END - 1.
  580.    If START is NULL, just flush the buffer. */
  581.  
  582. void
  583. output (start, past_end)
  584.      char *start;
  585.      char *past_end;
  586. {
  587.   static char buffer[WRITESIZE];
  588.   static int bytes_in_buffer = 0;
  589.   int bytes_to_add = past_end - start;
  590.   int bytes_available = WRITESIZE - bytes_in_buffer;
  591.  
  592.   if (start == 0)
  593.     {
  594.       xwrite (1, buffer, bytes_in_buffer);
  595.       bytes_in_buffer = 0;
  596.       return;
  597.     }
  598.   
  599.   /* Write out as many full buffers as possible. */
  600.   while (bytes_to_add >= bytes_available)
  601.     {
  602.       bcopy (start, buffer + bytes_in_buffer, bytes_available);
  603.       bytes_to_add -= bytes_available;
  604.       start += bytes_available;
  605.       xwrite (1, buffer, WRITESIZE);
  606.       bytes_in_buffer = 0;
  607.       bytes_available = WRITESIZE;
  608.     }
  609.  
  610.   bcopy (start, buffer + bytes_in_buffer, bytes_to_add);
  611.   bytes_in_buffer += bytes_to_add;
  612. }
  613.  
  614. void
  615. cleanup ()
  616. {
  617.   unlink (tempfile);
  618.   exit (1);
  619. }
  620.  
  621. void
  622. xwrite (desc, buffer, size)
  623.      int desc;
  624.      char *buffer;
  625.      int size;
  626. {
  627.   if (write (desc, buffer, size) != size)
  628.     {
  629.       error (0, errno, "write error");
  630.       cleanup ();
  631.       exit (1);
  632.     }
  633. }
  634.  
  635. /* Allocate N bytes of memory dynamically, with error checking.  */
  636.  
  637. char *
  638. xmalloc (n)
  639.      unsigned n;
  640. {
  641.   char *p;
  642.  
  643.   p = malloc (n);
  644.   if (p == 0)
  645.     {
  646.       cleanup ();
  647.       error (1, 0, "virtual memory exhausted");
  648.     }
  649.   return p;
  650. }
  651.  
  652. /* Change the size of memory area P to N bytes, with error checking. */
  653.  
  654. char *
  655. xrealloc (p, n)
  656.      char *p;
  657.      unsigned n;
  658. {
  659.   p = realloc (p, n);
  660.   if (p == 0)
  661.     {
  662.       cleanup ();
  663.       error (1, 0, "virtual memory exhausted");
  664.     }
  665.   return p;
  666. }
  667.